package com.iot.protocol.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;

public class UdpClient implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(UdpClient.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final Random random = new Random();
    private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

    private final int clientId;
    private final String serverHost;
    private final int serverPort;
    private final int sendInterval;
    private final AtomicBoolean running;
    private DatagramChannel channel;
    private final ByteBuffer receiveBuffer;

    public UdpClient(int clientId, String serverHost, int serverPort, int sendInterval) {
        this.clientId = clientId;
        this.serverHost = serverHost;
        this.serverPort = serverPort;
        this.sendInterval = sendInterval;
        this.running = new AtomicBoolean(true);
        this.receiveBuffer = ByteBuffer.allocate(1024);
    }

    public void connect() throws IOException {
        channel = DatagramChannel.open();
        channel.configureBlocking(false);
        channel.connect(new InetSocketAddress(serverHost, serverPort));
        logger.info("Client {} connected to {}:{}", clientId, serverHost, serverPort);
    }

    private SensorData generateData() {
        SensorData data = new SensorData();
        data.setClientId(clientId);
        data.setTimestamp(LocalDateTime.now().format(formatter));
        data.setTemperature(round(random.nextDouble(20.0, 30.0), 2));
        data.setHumidity(round(random.nextDouble(40.0, 60.0), 2));
        data.setPressure(round(random.nextDouble(980.0, 1020.0), 2));
        data.setBattery(round(random.nextDouble(20.0, 100.0), 2));
        return data;
    }

    private double round(double value, int places) {
        double scale = Math.pow(10, places);
        return Math.round(value * scale) / scale;
    }

    private void sendData() throws IOException {
        SensorData data = generateData();
        String jsonData = objectMapper.writeValueAsString(data);
        ByteBuffer buffer = ByteBuffer.wrap(jsonData.getBytes());
        channel.write(buffer);
        logger.info("Client {} sent: {}", clientId, jsonData);
    }

    private void receiveData() throws IOException {
        receiveBuffer.clear();
        InetSocketAddress sender = (InetSocketAddress) channel.receive(receiveBuffer);
        if (sender != null) {
            receiveBuffer.flip();
            byte[] bytes = new byte[receiveBuffer.remaining()];
            receiveBuffer.get(bytes);
            String response = new String(bytes);
            logger.info("Client {} received from {}: {}", clientId, sender, response);
        }
    }

    public void stop() {
        running.set(false);
        try {
            if (channel != null) {
                channel.close();
            }
        } catch (IOException e) {
            logger.error("Error closing channel for client {}", clientId, e);
        }
    }

    @Override
    public void run() {
        try {
            connect();
            while (running.get()) {
                sendData();
                receiveData();
                Thread.sleep(sendInterval);
            }
        } catch (IOException | InterruptedException e) {
            logger.error("Error in client {}: {}", clientId, e.getMessage(), e);
        } finally {
            stop();
        }
    }

    public static class SensorData {
        private int clientId;
        private String timestamp;
        private double temperature;
        private double humidity;
        private double pressure;
        private double battery;

        // Getters and setters
        public int getClientId() { return clientId; }
        public void setClientId(int clientId) { this.clientId = clientId; }
        public String getTimestamp() { return timestamp; }
        public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
        public double getTemperature() { return temperature; }
        public void setTemperature(double temperature) { this.temperature = temperature; }
        public double getHumidity() { return humidity; }
        public void setHumidity(double humidity) { this.humidity = humidity; }
        public double getPressure() { return pressure; }
        public void setPressure(double pressure) { this.pressure = pressure; }
        public double getBattery() { return battery; }
        public void setBattery(double battery) { this.battery = battery; }
    }
} 